Skip to content

perf(codegen): streamline async control cells - #8012

Merged
proggeramlug merged 2 commits into
PerryTS:mainfrom
proggeramlug:perf/8008-asyncpipe-gap
Aug 13, 2026
Merged

perf(codegen): streamline async control cells#8012
proggeramlug merged 2 commits into
PerryTS:mainfrom
proggeramlug:perf/8008-asyncpipe-gap

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Closes #8008.

Summary

  • lower compiler-private async/generator state, pending-type, done, and executing cells as direct typed loads/stores
  • retain the checked runtime box path for ordinary user captures
  • add an IR regression proving the private controls keep their primitive allocations while bypassing registry-checked access

Mechanism and sized lever

The async transform preallocates these four control cells and proves both their identity and primitive type. Their I32Box/BoolBox allocations are stable, single-field #[repr(C, align(8))] objects, but every state-machine get/set still called a runtime helper that revalidated the pointer against a thread-local registry.

On the refreshed pure-async profile, is_registered_i32_box_ptr and is_registered_bool_box_ptr account for 7.13% of leaf samples. Direct typed access to only the compiler-minted controls removes that lookup; allocation and registry insertion remain unchanged, and unproven/user boxes retain the #4898 checked path. The measured pure-async instruction reduction is 7.08%.

Current-main decomposition and A/B

Current main is f58b73f (0.5.1508). Both arms use the exact same f58b73f runtime/stdlib archives with auto-optimize disabled, isolating compiler codegen from runtime and feature-set drift. Retired instructions and cycles are best of 13 interleaved runs; wall is median of the same 13. The host load was high, so retired instructions are the primary result and wall is reported as required directional evidence.

probe current main instr this PR instr delta instr delta cycles wall (main -> PR)
a2_full1x 1207.2 M 1181.9 M -2.10% -0.61% 97.35 -> 95.35 ms (-2.06%)
a2_sync1x 247.1 M 247.0 M -0.02% -1.22% 23.86 -> 24.24 ms (+1.59%)
a2_pure1x 622.4 M 578.3 M -7.08% -5.97% 47.64 -> 45.94 ms (-3.57%)
a2_pureobj1x 748.4 M 704.1 M -5.92% -5.97% 62.05 -> 60.33 ms (-2.77%)
a2_pall1x 397.3 M 397.0 M -0.07% -1.97% 39.91 -> 38.65 ms (-3.16%)

The original 85.2% async share is now 79.5% on current main (1 - 247.1 / 1207.2) after #7927, #7924, #7939, and subsequent mainline work. This patch reduces the full probe another 2.10%, while the synchronous and Promise.all-only instruction controls are flat. The selective pure-async result sizes the named control-cell validation mechanism rather than attributing the remaining gap to undifferentiated “async overhead.”

Validation

  • cargo test --release -p perry-codegen (939 unit tests, 263 native proof regressions, integrations and doctests)
  • post-rebase focused native proof regression
  • cargo clippy -p perry-codegen --lib --tests
  • ./scripts/pre-tag-check.sh --quick
  • generator parity: 6/6
  • async parity: 40/42; the two issue-915 cases were environment-only failures because local Node dependencies fastify/jsonwebtoken were absent (the other 40 passed)
  • full probe under forced evacuation + verification + from-space protection

No version bump is included.

Summary by CodeRabbit

  • Performance

    • Improved async and generator control-cell access for more efficient execution.
    • Reduced generated instructions for internal control operations.
  • Bug Fixes

    • Preserved safe checked access for user-managed captured values.
    • Added regression coverage to ensure async control-cell reads and writes remain correctly typed.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1cc70d4c-359c-4a84-8c32-d34f582edef8

📥 Commits

Reviewing files that changed from the base of the PR and between d5cdf3f and 5bec742.

📒 Files selected for processing (1)
  • changelog.d/8012-async-control-cells.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • changelog.d/8012-async-control-cells.md

📝 Walkthrough

Walkthrough

Compiler-private async and generator control cells now use direct typed LLVM payload loads and stores for preallocated I32Box and BoolBox values. Initialization, comparisons, reads, and writes use dedicated helpers. Regression coverage verifies typed access and preserved allocation behavior.

Changes

Async control-cell access

Layer / File(s) Summary
Typed cell access and wiring
crates/perry-codegen/src/expr/mod.rs, crates/perry-codegen/src/stmt/let_stmt.rs
Added direct i32 and i1 load/store helpers. Async control comparisons, reads, writes, and initialization now use typed cell access.
Regression coverage and changelog
crates/perry-codegen/tests/native_proof_regressions.rs, changelog.d/8012-async-control-cells.md
The regression test checks primitive allocation, rejects checked box access, and verifies typed loads and stores. The changelog records the change and benchmark results.

Estimated code review effort: 2 (Simple) | ~10 minutes

Mergeability Score: 🟡 Moderate · up to 5bec7

This change uses direct access for compiler-private async control cells; if an access occurs before the cell is preallocated, generated code could crash. That unresolved correctness risk should be fixed or explicitly accepted before merging.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the code-generation performance change for async control cells.
Description check ✅ Passed The description provides a detailed summary, linked issue, implementation scope, measurements, and validation results, despite missing some template headings and checklist items.
Linked Issues check ✅ Passed The PR satisfies issue #8008 by re-measuring current main, naming a sized registry-validation lever, and reporting retired instructions with wall-clock results.
Out of Scope Changes check ✅ Passed The code, regression test, and changelog entry directly support the async control-cell optimization and linked performance objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/perry-codegen/tests/native_proof_regressions.rs (1)

7098-7122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Expand the regression to cover the full eligibility boundary.

The fixture declares __gen_state, __gen_done, and __gen_executing, but not __gen_pending_type. It also contains no ordinary user capture. These assertions can pass while __gen_pending_type still uses checked access or while an ordinary capture is incorrectly lowered through the direct path.

Add a pending-type read/write and a paired ordinary-capture case, or verify that existing tests cover both paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/native_proof_regressions.rs` around lines 7098 -
7122, Expand the regression fixture and assertions around the existing
control-lowering checks to cover __gen_pending_type reads/writes and a paired
ordinary user capture. Verify that pending-type access uses the intended direct
typed path, while the ordinary capture retains checked box access, ensuring both
sides of the eligibility boundary are exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-codegen/src/expr/mod.rs`:
- Around line 2300-2333: Replace the name-based checks in
is_compiler_private_async_i32_control_local and related classifiers with
compiler-generated identity/IDs, or reserve the __gen_state, __gen_pending_type,
__gen_done, and __gen_executing names so user locals cannot match them. Ensure
ordinary captured user bindings continue through boxed-variable lowering without
primitive-cell truncation, and add a regression covering a captured user binding
with one of these names.

---

Nitpick comments:
In `@crates/perry-codegen/tests/native_proof_regressions.rs`:
- Around line 7098-7122: Expand the regression fixture and assertions around the
existing control-lowering checks to cover __gen_pending_type reads/writes and a
paired ordinary user capture. Verify that pending-type access uses the intended
direct typed path, while the ordinary capture retains checked box access,
ensuring both sides of the eligibility boundary are exercised.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a5e8a3d3-6ef9-44bf-8f20-bfe25ac5cdbf

📥 Commits

Reviewing files that changed from the base of the PR and between f58b73f and d5cdf3f.

📒 Files selected for processing (4)
  • changelog.d/8012-async-control-cells.md
  • crates/perry-codegen/src/expr/mod.rs
  • crates/perry-codegen/src/stmt/let_stmt.rs
  • crates/perry-codegen/tests/native_proof_regressions.rs

Comment on lines +2300 to +2333
/// Load a compiler-private async i32 control cell directly.
///
/// These cells are allocated by `Stmt::PreallocateBoxes` before the generated
/// state-machine closures are created. Unlike a general user capture, the
/// pointer is therefore compiler-minted and its pointee representation is
/// proven: the `I32Box` value is the first (and only) field. Keep ordinary
/// boxes on the checked runtime path; this helper is deliberately reachable
/// only from the `is_compiler_private_async_i32_control_local` arms below.
pub(crate) fn load_async_i32_control_cell(ctx: &mut FnCtx<'_>, cell: &str) -> String {
let ptr = ctx.block().inttoptr(I64, cell);
ctx.block().load(I32, &ptr)
}

/// Store a compiler-private async i32 control cell directly. See
/// `load_async_i32_control_cell` for the allocation/provenance proof.
pub(crate) fn store_async_i32_control_cell(ctx: &mut FnCtx<'_>, cell: &str, value: &str) {
let ptr = ctx.block().inttoptr(I64, cell);
ctx.block().store(I32, value, &ptr);
}

/// Load a compiler-private async boolean control cell directly. `BoolBox`'s
/// value is a Rust `bool`, represented as LLVM i1 at the FFI boundary.
pub(crate) fn load_async_i1_control_cell(ctx: &mut FnCtx<'_>, cell: &str) -> String {
let ptr = ctx.block().inttoptr(I64, cell);
ctx.block().load(I1, &ptr)
}

/// Store a compiler-private async boolean control cell directly. See
/// `load_async_i1_control_cell` for the representation proof.
pub(crate) fn store_async_i1_control_cell(ctx: &mut FnCtx<'_>, cell: &str, value: &str) {
let ptr = ctx.block().inttoptr(I64, cell);
ctx.block().store(I1, value, &ptr);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

ast-grep outline crates/perry-codegen/src/expr/mod.rs \
  --match 'load_async_i32_control_cell' --view expanded
ast-grep outline crates/perry-codegen/src/stmt/mod.rs \
  --match 'emit_preallocate_boxes' --view expanded

rg -n -C 14 \
  'PreallocateBoxes|collect_prealloc_box_ids_in_stmts|compiler_private_async_i32_control_locals|compiler_private_async_i1_control_locals|is_compiler_private_async_i32_control_local|is_compiler_private_async_i1_control_local|load_boxed_local_pointer|load_async_i32_control_cell|store_async_i32_control_cell|load_async_i1_control_cell|store_async_i1_control_cell' \
  crates/perry-codegen crates/perry-transform

Repository: PerryTS/perry

Length of output: 50372


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- expr helpers and predicates ---'
sed -n '2240,2350p' crates/perry-codegen/src/expr/mod.rs

printf '%s\n' '--- statement lowering and preallocation ---'
sed -n '540,690p' crates/perry-codegen/src/stmt/mod.rs

printf '%s\n' '--- direct helper call sites ---'
rg -n -C 8 \
  'load_async_i32_control_cell|store_async_i32_control_cell|load_async_i1_control_cell|store_async_i1_control_cell|is_compiler_private_async_i32_control_local|is_compiler_private_async_i1_control_local' \
  crates/perry-codegen/src

printf '%s\n' '--- all PreallocateBoxes construction sites ---'
rg -n -C 10 \
  'PreallocateBoxes|PreallocateTdzBoxes|emit_preallocate_boxes|collect_prealloc_box_ids_in_stmts' \
  crates/perry-transform/src crates/perry-codegen/src \
  -g '*.rs' \
  | head -n 1400

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

roots = [Path("crates/perry-transform/src"), Path("crates/perry-codegen/src")]
files = [p for root in roots for p in root.rglob("*.rs")]

for p in files:
    text = p.read_text()
    for match in re.finditer(r"PreallocateBoxes|PreallocateTdzBoxes", text):
        line = text.count("\n", 0, match.start()) + 1
        start = max(0, line - 4)
        lines = text.splitlines()
        end = min(len(lines), line + 8)
        print(f"\n--- {p}:{line} ---")
        for n in range(start + 1, end + 1):
            print(f"{n}: {lines[n-1]}")
PY

Repository: PerryTS/perry

Length of output: 50371


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- complete preallocation lowering ---'
sed -n '680,755p' crates/perry-codegen/src/stmt/mod.rs

printf '%s\n' '--- generator body construction ---'
sed -n '620,745p' crates/perry-transform/src/generator/lower.rs

printf '%s\n' '--- async transform control-local setup ---'
rg -n -C 18 \
  'compiler_private_async_i32_control_locals|compiler_private_async_i1_control_locals|async_i32|async_i1|control_local|control cell|PreallocateBoxes' \
  crates/perry-transform/src/async_to_generator.rs \
  crates/perry-transform/src/generator \
  crates/perry-codegen/src/codegen \
  crates/perry-codegen/src/expr \
  -g '*.rs' \
  | head -n 1800

printf '%s\n' '--- all private-set references ---'
rg -n -C 6 \
  'compiler_private_async_i32_control_locals|compiler_private_async_i1_control_locals' \
  crates -g '*.rs'

Repository: PerryTS/perry

Length of output: 50372


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

needles = (
    "compiler_private_async_i32_control_locals",
    "compiler_private_async_i1_control_locals",
)
for p in Path("crates").rglob("*.rs"):
    lines = p.read_text().splitlines()
    for i, line in enumerate(lines):
        if any(n in line for n in needles):
            lo = max(0, i - 20)
            hi = min(len(lines), i + 21)
            print(f"\n--- {p}:{i+1} ---")
            for n in range(lo, hi):
                print(f"{n+1}: {lines[n]}")
PY

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- private-set mutations and definitions ---'
rg -n \
  'compiler_private_async_i32_control_locals|compiler_private_async_i1_control_locals' \
  crates/perry-codegen crates/perry-transform \
  -g '*.rs' \
  | grep -E 'insert|extend|union|HashSet|pub |fn |struct |=' \
  | head -n 500

printf '%s\n' '--- all async control-local identifiers and types ---'
rg -n -C 10 \
  '__gen_state|__gen_done|__gen_executing|__gen_pending_type|__gen_pending_value|pending_type_id|state_id|done_id|executing_id' \
  crates/perry-transform/src/generator \
  -g '*.rs' \
  | head -n 1800

printf '%s\n' '--- function-body lowering order ---'
rg -n -C 18 \
  'lower_stmts|lower_stmt|func.body|collect_prealloc_box_ids_in_stmts|prealloc_boxes' \
  crates/perry-codegen/src/codegen crates/perry-codegen/src/stmt crates/perry-codegen/src/expr \
  -g '*.rs' \
  | head -n 2200

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

for p in Path("crates").rglob("*.rs"):
    text = p.read_text()
    for name in (
        "compiler_private_async_i32_control_locals",
        "compiler_private_async_i1_control_locals",
    ):
        for m in re.finditer(re.escape(name), text):
            line = text.count("\n", 0, m.start()) + 1
            before = text[max(0, m.start()-180):m.start()]
            after = text[m.end():m.end()+220]
            if any(op in before[-80:] + after[:120] for op in ("insert", "extend", "union", "collect", "HashSet", "=")):
                print(f"{p}:{line}: {text.splitlines()[line-1].strip()}")
PY

Repository: PerryTS/perry

Length of output: 2472


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- private-ID scanner population ---'
sed -n '1540,1650p' crates/perry-codegen/src/codegen/mod.rs
sed -n '1700,1790p' crates/perry-codegen/src/codegen/mod.rs

printf '%s\n' '--- scanner definitions and call sites ---'
rg -n -C 24 \
  'collect_compiler_private|compiler_private_async|__gen_state|__gen_done|__gen_executing|__gen_pending' \
  crates/perry-codegen/src/codegen/mod.rs \
  crates/perry-codegen/src \
  -g '*.rs' \
  | head -n 1800

printf '%s\n' '--- function lowering entry and body traversal ---'
rg -n -C 22 \
  'lower_stmts|lower_stmt|f.body|method.body|body.iter|lower_function|lower_method' \
  crates/perry-codegen/src/codegen/function.rs \
  crates/perry-codegen/src/codegen/method.rs \
  crates/perry-codegen/src/codegen/closure.rs \
  crates/perry-codegen/src/stmt/mod.rs \
  -g '*.rs' \
  | head -n 2200

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

p = Path("crates/perry-codegen/src/codegen/mod.rs")
lines = p.read_text().splitlines()
for i, line in enumerate(lines):
    if "compiler_private_async" in line or "collect_compiler_private" in line:
        lo, hi = max(0, i-35), min(len(lines), i+45)
        print(f"\n--- {p}:{i+1} ---")
        for n in range(lo, hi):
            print(f"{n+1}: {lines[n]}")
PY

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- compiler-private collector definition ---'
rg -n 'fn collect_compiler_private_async_control_locals_in_stmts|collect_compiler_private_async_control_locals_in_stmts' \
  crates/perry-codegen/src/boxed_vars.rs
sed -n '1,180p' crates/perry-codegen/src/boxed_vars.rs

printf '%s\n' '--- closure lowering and capture slot setup ---'
rg -n -C 20 \
  'closure_captures|load_boxed_local_pointer|js_closure_get_capture_bits|collect_prealloc_box_ids_in_stmts|PreallocateBoxes' \
  crates/perry-codegen/src/codegen/closure.rs \
  crates/perry-codegen/src/boxed_vars.rs \
  crates/perry-codegen/src/expr/mod.rs \
  -g '*.rs' \
  | head -n 1800

printf '%s\n' '--- codegen body lowering calls ---'
rg -n -C 16 \
  'lower_stmts|lower_stmt|lower_expr|f.body|body.as_slice|body.iter' \
  crates/perry-codegen/src/codegen/function.rs \
  crates/perry-codegen/src/codegen/method.rs \
  crates/perry-codegen/src/codegen/closure.rs \
  -g '*.rs' \
  | head -n 1800

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

p = Path("crates/perry-codegen/src/boxed_vars.rs")
lines = p.read_text().splitlines()
for i, line in enumerate(lines):
    if "collect_compiler_private_async_control_locals_in_stmts" in line:
        lo, hi = max(0, i-8), min(len(lines), i+120)
        print(f"--- {p}:{i+1} ---")
        for n in range(lo, hi):
            print(f"{n+1}: {lines[n]}")
PY

Repository: PerryTS/perry

Length of output: 50369


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- all PreallocateBoxes constructors ---'
rg -n -C 5 \
  'Stmt::PreallocateBoxes\(|Stmt::PreallocateTdzBoxes\(' \
  crates -g '*.rs'

printf '%s\n' '--- internal control names outside generator lowering ---'
rg -n -C 5 \
  '__gen_state|__gen_pending_type|__gen_done|__gen_executing' \
  crates tests test-files 2>/dev/null \
  | head -n 1800

printf '%s\n' '--- tests for private control cells and preallocation ---'
rg -n -C 8 \
  'compiler_private_async|primitive_i32_control_cell|primitive_i1_control_cell|PreallocateBoxes|__gen_state|__gen_done|__gen_executing|__gen_pending_type' \
  crates tests test-files 2>/dev/null \
  | head -n 2200

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

# Read-only structural verifier for the invariant relevant to this review.
files = [p for root in (Path("crates/perry-transform/src"), Path("crates/perry-codegen/src"))
         for p in root.rglob("*.rs")]
constructors = []
for p in files:
    for i, line in enumerate(p.read_text().splitlines(), 1):
        if re.search(r'Stmt::Preallocate(?:Tdz)?Boxes\s*\(', line):
            constructors.append((str(p), i, line.strip()))

print("PREALLOC_CONSTRUCTORS")
for item in constructors:
    print(f"{item[0]}:{item[1]}:{item[2]}")

lower = Path("crates/perry-transform/src/generator/lower.rs").read_text()
print("\nGENERATOR_FIRST_PREALLOC",
      bool(re.search(r'new_body\.push\(Stmt::PreallocateBoxes\(prealloc_ids\)\);', lower)))

collector = Path("crates/perry-codegen/src/boxed_vars.rs").read_text()
rules = {
    "i32_names": bool(re.search(r'"__gen_state"\s*\|\s*"__gen_pending_type"', collector)),
    "i1_names": bool(re.search(r'"__gen_done"\s*\|\s*"__gen_executing"', collector)),
    "requires_preallocated": bool(re.search(r'if preallocated\.contains\(id\)', collector)),
}
print("COLLECTOR_RULES", rules)

for name in ("__gen_state", "__gen_pending_type", "__gen_done", "__gen_executing"):
    occurrences = []
    for p in files:
        for i, line in enumerate(p.read_text().splitlines(), 1):
            if name in line:
                occurrences.append(f"{p}:{i}")
    print(name, len(occurrences), *occurrences[:12], sep=" | ")
PY

Repository: PerryTS/perry

Length of output: 12687


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- all-repository preallocation construction ---'
rg -n -C 8 \
  'PreallocateBoxes|PreallocateTdzBoxes|prealloc.*box|preallocate.*box' \
  . -g '*.rs' \
  | grep -E 'push|insert|emit|lower|Preallocate' \
  | head -n 1800

printf '%s\n' '--- control-value coercion helpers ---'
rg -n -C 18 \
  'lower_i32_control_store_value|lower_i1_control_store_value|compiler_private_async_control' \
  crates/perry-codegen/src/expr/mod.rs \
  crates/perry-codegen/src/stmt/let_stmt.rs \
  -g '*.rs' \
  | head -n 1600

printf '%s\n' '--- generated-name reservation or user-name filtering ---'
rg -n -C 8 \
  '__gen_state|__gen_pending_type|__gen_done|__gen_executing|reserved.*name|internal.*name|name.*reserved' \
  crates/perry-hir crates/perry-transform crates/perry-codegen \
  -g '*.rs' \
  | head -n 1800

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

p = Path("crates/perry-codegen/src/expr/mod.rs")
lines = p.read_text().splitlines()
for i, line in enumerate(lines):
    if "lower_i32_control_store_value" in line or "lower_i1_control_store_value" in line:
        lo, hi = max(0, i-8), min(len(lines), i+65)
        print(f"\n--- {p}:{i+1} ---")
        for n in range(lo, hi):
            print(f"{n+1}: {lines[n]}")
PY

Repository: PerryTS/perry

Length of output: 13622


Avoid name-based classification of user locals

A preallocated user binding named __gen_state, __gen_pending_type, __gen_done, or __gen_executing matches the compiler-private classifier in crates/perry-codegen/src/boxed_vars.rs:1427-1438. Its writes then use primitive-cell lowering, so a Number binding can be truncated to i32. Use compiler-generated IDs or reserve these names. Add a regression with a captured user binding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/expr/mod.rs` around lines 2300 - 2333, Replace the
name-based checks in is_compiler_private_async_i32_control_local and related
classifiers with compiler-generated identity/IDs, or reserve the __gen_state,
__gen_pending_type, __gen_done, and __gen_executing names so user locals cannot
match them. Ensure ordinary captured user bindings continue through
boxed-variable lowering without primitive-cell truncation, and add a regression
covering a captured user binding with one of these names.

Source: Coding guidelines

@proggeramlug
proggeramlug force-pushed the perf/8008-asyncpipe-gap branch from d5cdf3f to 5bec742 Compare August 13, 2026 04:20
@proggeramlug
proggeramlug merged commit 201bb3c into PerryTS:main Aug 13, 2026
30 of 56 checks passed
@proggeramlug
proggeramlug deleted the perf/8008-asyncpipe-gap branch August 13, 2026 04:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: async lowering is 85% of asyncpipe — Perry pays 6.8x what Node pays for the same async/sync difference

1 participant